/**
 * 
 */
package storage;

import general.Flag;
import general.Status;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.Callable;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.j256.ormlite.dao.BaseDaoImpl;
import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.jdbc.JdbcConnectionSource;
import com.j256.ormlite.misc.TransactionManager;
import com.j256.ormlite.stmt.PreparedQuery;
import com.j256.ormlite.stmt.QueryBuilder;
import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.table.TableUtils;

/**
 * @author Last G
 *
 */
public class ORMLite implements StorageAdapter
{
	private ConnectionSource connectionSource;
	private static final Logger log = LoggerFactory.getLogger(ORMLite.class);
	Dao<Flag, Integer> flagDAO = null;

	
	/**
	 * Constructor
	 * @param config configurations
	 * @throws StorageException if something is wrong
	 */
	public ORMLite(Properties config) throws StorageException
	{
		String url = config.getProperty("storage.db.jdbc");
		if (url == null)
		{
			String msg = "There is no jdbc connection string in config";
			throw new StorageException(msg);
		}
		try
		{
			connectionSource = new JdbcConnectionSource(url);
		} catch (SQLException e)
		{
			throw new StorageException("Can't create connection with JDBC url: '" + url + "'", e);
		}
		
		try
		{
			flagDAO = BaseDaoImpl.createDao(connectionSource, Flag.class);
		} catch (SQLException e)
		{
			throw new StorageException("Can't initialize DAO(Problem in ORM)", e);
		}
		
		try
		{
			if(!flagDAO.isTableExists())
			{
				TableUtils.createTable(connectionSource, Flag.class);
			}
		} catch (SQLException e)
		{
			log.error("Can't create table", e);
		}
	}
	
	/* (non-Javadoc)
	 * @see storage.StorageAdapter#putFlag(general.Flag)
	 */
	@Override
	public int putFlag(Flag fl) throws StorageException
	{
		// Check for existance
		try
		{
			flagDAO.create(fl);
			flagDAO.refresh(fl);
		} catch (SQLException e)
		{
			fl = getFlag(fl.getFlag());
		}
		return fl.getId();
	}

	/* (non-Javadoc)
	 * @see storage.StorageAdapter#getFlag(int)
	 */
	@Override
	public Flag getFlag(int id) throws StorageException
	{
		try
		{
			return flagDAO.queryForId(id);
		} catch (SQLException e)
		{
			log.error(e.getLocalizedMessage());
			return null;
		}
	}

	/* (non-Javadoc)
	 * @see storage.StorageAdapter#getFlagAndLock(int)
	 */
	@Override
	public Flag getFlagAndLock(final int id) throws StorageException
	{
		final Flag result = new Flag();
		result.setId(id);
		TransactionManager transactionManager = new TransactionManager(connectionSource);
		try
		{
			transactionManager.callInTransaction(new Callable<Void>()
				{
					public Void call() throws StorageException
					{
						try
						{
							flagDAO.refresh(result);
							result.setStatus(Status.Busy);
							flagDAO.update(result);
						} catch (Exception e)
						{
							log.error("Can't get's flag and lock", e);
							throw new StorageException("Transaction failed.", e);
						}
						return null;
					}
				});
		}
		catch (Exception e) 
		{
			log.error("Something go's wrong in transaction", e);
			return null;
		}
		
		return result;
	}

	/* (non-Javadoc)
	 * @see storage.StorageAdapter#getFlags(general.Status, int)
	 */
	@Override
	public Flag[] getFlags(Status state, int limit) throws StorageException
	{
		final List<Flag> result = new ArrayList<Flag>();
		
		TransactionManager transactionManager = new TransactionManager(connectionSource);
		try
		{
			// Create new query with Limit
			QueryBuilder<Flag, Integer> builder = flagDAO.queryBuilder().limit(limit);
			// Get only flags, that we need
			builder.where().eq("status", state);
			// Preparing query
			final PreparedQuery<Flag> preparedQuery = builder.prepare();
			
			transactionManager.callInTransaction(new Callable<Void>()
				{
					public Void call() throws StorageException
					{
						try
						{
							result.addAll(flagDAO.query(preparedQuery));
						} catch (Exception e)
						{
							log.error("Can't get flags", e);
							throw new StorageException("Transaction failed.", e);
						}
						return null;
					}
				});
		}
		catch (Exception e) 
		{
			log.error("Something go's wrong in transaction", e);
		}
		
		return result.toArray(new Flag[0]);
		
	}

	/* (non-Javadoc)
	 * @see storage.StorageAdapter#getFlagsAndLock(general.Status, int)
	 */
	@Override
	public Flag[] getFlagsAndLock(final Status state, int limit)
			throws StorageException
	{
		final List<Flag> result = new ArrayList<Flag>();
		
		TransactionManager transactionManager = new TransactionManager(connectionSource);
		try
		{
			// Create new query with Limit
			QueryBuilder<Flag, Integer> builder = flagDAO.queryBuilder().limit(limit);
			// Get only flags, that we need
			builder.where().eq("status", state);
			// Preparing query
			final PreparedQuery<Flag> preparedQuery = builder.prepare();
			
			transactionManager.callInTransaction(new Callable<Void>()
				{
					public Void call() throws StorageException
					{
						try
						{
							result.addAll(flagDAO.query(preparedQuery));
							for(Flag fl : result)
							{
								fl.setStatus(Status.Busy);
								flagDAO.update(fl);
							}
						} catch (Exception e)
						{
							log.error("Can't get flags", e);
							throw new StorageException("Transaction failed.", e);
						}
						return null;
					}
				});
		}
		catch (Exception e) 
		{
			log.error("Something go's wrong in transaction", e);
		}
		
		return result.toArray(new Flag[0]);			
	}

	/* (non-Javadoc)
	 * @see storage.StorageAdapter#updateStatus(general.Flag[], general.Status[])
	 */
	@Override
	public void updateStatus(Flag[] flags, Status[] statuses)
			throws StorageException
	{
		for(int i =0; i < Math.min(flags.length, statuses.length); ++i)
		{
			flags[i].setStatus(statuses[i]);
			
			boolean end = false;
			for(int k=0; k < 15 && !end; ++k)
			{
				try
				{
					end = (flagDAO.update(flags[i]) >= 0);
				} catch (Exception e)
				{
					log.warn("Exception when trying update flags", e);
				}
			}
			if(!end)
			{
				throw new StorageException("Can't update flag status in databse");
			}
		}
	}

	@Override
	public void flushDB() throws StorageException
	{
		try
		{
			TableUtils.dropTable(connectionSource, Flag.class, true);
		} catch (SQLException e)
		{
			// just ignore
		}
		try
		{
			TableUtils.createTable(connectionSource, Flag.class);
		} catch (Exception e)
		{
			throw new StorageException("Can't flush tables", e);
		}
	}
	
	private Flag getFlag(String flag) throws StorageException
	{
		try
		{
			QueryBuilder<Flag, Integer> builder = flagDAO.queryBuilder().limit(1);
			// Get only flags, that we need
			builder.where().eq("flag", flag);
			// Preparing query
			PreparedQuery<Flag> preparedQuery = builder.prepare();
		
			return flagDAO.queryForFirst(preparedQuery);
		} catch (SQLException e)
		{
			throw new StorageException("Can't query for flag", e);
		}
	}

}
